/**
 * 
 */
package net.sf.cldsql.dj.db;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;

import net.sf.cldsql.dj.container.DiContainer;
import net.sf.cldsql.dj.container.base.DiContext;

/**
 * QueryProxy
 */
public class QueryProxy<T> implements InvocationHandler {
    
    private Class<T> queryClass;
    private Connection conn;
    private Logger log;
    private DiContainer container;

    public QueryProxy (Class<T> queryClass) {
        this.queryClass = queryClass;
    }
    public T newInstance() {
        return (T)Proxy.newProxyInstance(
                queryClass.getClassLoader(), 
                new Class[] {queryClass}, 
                this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("setConn")) {
            this.conn = (Connection)args[0];
            return null;
        } else if (method.getName().equals("setLog")) {
            this.log = (Logger)args[0];
            return null;
        } else if (method.getName().equals("setContainer")) {
            this.container = (DiContainer)args[0];
            return null;
        } else if (method.getName().equals("toString")) {
            return queryClass.getName();
        } else if (method.getName().startsWith("exec")) {
            long beginTs = System.currentTimeMillis();
            
            String sqlFilename = queryClass.getSimpleName() + "." + method.getName() + ".sql";
            log.debug("Load sql from file {} ", sqlFilename);
            BufferedReader reader = new BufferedReader(new InputStreamReader(queryClass.getResourceAsStream(sqlFilename), "UTF-8"));
            StringBuilder buf = new StringBuilder();
            for (String line; (line = reader.readLine()) != null;) {
                buf.append(line).append('\n');
            }
            String sqlSrc = buf.toString();
            DiContext context = (DiContext)args[0];
            Map<String, Object> paramMap = (Map<String, Object>)args[1];
            ArrayList<Object> paramList = new ArrayList<Object>();
            
            Matcher matcher = Pattern.compile("#([^#]+)#").matcher(sqlSrc);
            int curPos = 0;
            StringBuilder sql = new StringBuilder();
            for (; matcher.find(); curPos = matcher.end() + 1) {
                sql.append(sqlSrc.substring(curPos, matcher.start()));
                sql.append("?");
                paramList.add(paramMap.get(matcher.group(1)));
            }
            sql.append(sqlSrc.substring(curPos));

            SqlBind sqlBind = new SqlBind(sql.toString(), paramList);
            log.debug("parsed sql {} paramList {} .", sql, paramList);
            
            PreparedStatement stmt = conn.prepareStatement(sqlBind.getSql());
            for (int i = 0; i < sqlBind.getParameters().size(); i ++) {
                stmt.setString(i + 1, (String) sqlBind.getParameters().get(i));
            }
            
            long execTs = System.currentTimeMillis();
            boolean hasResult = stmt.execute();
            log.debug("sql execute time {} ms .", System.currentTimeMillis() - execTs);
            
            long fetchTs = System.currentTimeMillis();
            ArrayList<Map<String, String>> result = null;
            if (hasResult) {
                result = new ArrayList<Map<String, String>>();
                ResultSet rset = stmt.getResultSet();
                for (int rownum = 0; rset.next(); rownum ++) {
                    Map<String, String> colMap = new HashMap<String, String>();
                    for (int i = 0; i < rset.getMetaData().getColumnCount(); i ++) {
                        colMap.put(rset.getMetaData().getColumnName(i + 1), rset.getString(i + 1));
                    }
                    result.add(colMap);
                }
            }
            log.debug("sql fetch time {} ms .", System.currentTimeMillis() - fetchTs);
            
            log.info("executed cost {} ms | sql {} | param {} | result {}", System.currentTimeMillis() - beginTs, sql, paramList, result);
            
            return (Object)result;
            
        } else {
            if (log != null) log.debug("Unknown method {}", method);
            return null;
        }
    }

}
